/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiThread.cpp
	An cross-platform interface for dealing with multithreaded functionality.

******************************************************************************/

#include "MaxsiLibrary.h"

BeginMaxsiNamespace

//=============================================================================
//	Create a static instance of the local multithreading system for this
//	platform that should be accessed through the ThreadSystem() call.
//=============================================================================
#if defined(Maxsi_Support_W32)
MaxsiThreadSystemW32
#elif defined(Maxsi_Support_Pthreads)
MaxsiThreadSystemPthreads
#else
#error There is no default multithreading intCreateEventerface for your platform that implements MaxsiThreadSystem!
#endif
/*MaxsiThreadSystem*/ LocalThreadSystem;

MaxsiThreadSystem* ThreadSystem()
{
	return (MaxsiThreadSystem*)&LocalThreadSystem;
}

size_t GetNumberOfCPUs()
{
#ifdef Maxsi_Support_W32

	SYSTEM_INFO info;

	GetSystemInfo(&info);

	return (size_t)info.dwNumberOfProcessors;

#elif defined(_SC_NPROCESSORS_ONLN)

	MAXSI_TODO("The _SC_NPROCESSORS_ONLN and _SC_NPROCESSORS_CONF macros are not defined by POSIX! This might be non-standard code!");

	// Detect number of online CPUs.
	long nprocs = sysconf(_SC_NPROCESSORS_ONLN);

	if (nprocs < 1) { return 0; } // MAXSI_ERROR_UNSPECIFIED

	// Detect maximum number of CPUs.
	long nprocs_max = sysconf(_SC_NPROCESSORS_CONF);

	if (nprocs_max < 1) { return 0; } // MAXSI_ERROR_UNSPECIFIED

	return (size_t)nprocs;

#else

	#error "A method to detect the number of CPUs on your platform has not been implemented or is not available!"

	return 0; // MAXSI_ERROR_NOT_SUPPORTED

#endif	
}


MaxsiThreadSystem::MaxsiThreadSystem() { }
MaxsiThreadSystem::~MaxsiThreadSystem() { }

//=============================================================================
//	Implement Pthreads
//=============================================================================
#ifdef Maxsi_Support_Pthreads

#include <pthread.h>

void* PthreadsHelloIAmANewThread(void* Parameter)
{
	MaxsiThreadEntry_t	Function			=	((MaxsiThreadParameter*)Parameter)->Function;
	BYTE*				FunctionParameter	=	((MaxsiThreadParameter*)Parameter)->FunctionParameter;

	delete (MaxsiThreadParameter*)(Parameter);
	
	Function(FunctionParameter);
	
	pthread_exit(NULL);
}

MaxsiHandle MaxsiThreadSystemPthreads::CreateThread(MaxsiThreadEntry_t Entry, BYTE* Argument, size_t StackSize)
{
	pthread_t*	ThreadId	=	new pthread_t;

	if ( ThreadId == NULL ) { return NULL; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	MaxsiThreadParameter*	Parameter	=	new MaxsiThreadParameter;

	if ( Parameter == NULL ) { delete ThreadId; return NULL; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	Parameter->Function				=	Entry;
	Parameter->FunctionParameter	=	Argument;

	pthread_attr_t 	Attributes;

	// Initialize the thread attributes with the default values and handle error conditions.
	if( pthread_attr_init(&Attributes) != 0 ) { delete Parameter; delete ThreadId; return NULL; } // MAXSI_ERROR_MEM_ALLOC_FAIL
	
	pthread_attr_setstacksize(&Attributes, StackSize);

	int	ReturnValue		=	pthread_create(ThreadId, &Attributes, PthreadsHelloIAmANewThread, Parameter);

	pthread_attr_destroy(&Attributes);

	if ( ReturnValue != 0 ) { delete Parameter; delete ThreadId; return NULL; }

	return (MaxsiHandle)ThreadId;
}

MaxsiHandle MaxsiThreadSystemPthreads::ExitThread(int ExitCode)
{
	MAXSI_TODO("What do we do about ExitCode?");
	pthread_exit(NULL);
	return NULL;
}

MaxsiHandle MaxsiThreadSystemPthreads::GetThreadId()
{
	return (MaxsiHandle)pthread_self();
}

bool MaxsiThreadSystemPthreads::CompareThreadIds(MaxsiHandle Handle1, MaxsiHandle Handle2)
{
	return (pthread_equal((pthread_t)Handle1, (pthread_t)Handle2));
}

MaxsiHandle MaxsiThreadSystemPthreads::CreateCriticalSection()
{
	pthread_mutex_t*	Mutex	=	new pthread_mutex_t;

	if ( Mutex == NULL ) { return NULL; }

	if ( pthread_mutex_init(Mutex, NULL) != 0 ) { delete Mutex; return NULL; }

	return (MaxsiHandle)Mutex;
}

bool MaxsiThreadSystemPthreads::DeleteCriticalSection(MaxsiHandle CriticalSection)
{
	if (pthread_mutex_destroy((pthread_mutex_t*)CriticalSection)==0)
	{
		delete (pthread_mutex_t*)CriticalSection;
		return true;
	}

	return false;
}

bool MaxsiThreadSystemPthreads::EnterCriticalSection(MaxsiHandle CriticalSection)
{
	return (pthread_mutex_lock((pthread_mutex_t*)CriticalSection)==0);
}

bool MaxsiThreadSystemPthreads::TryEnterCriticalSection(MaxsiHandle CriticalSection)
{
	return (pthread_mutex_trylock((pthread_mutex_t*)CriticalSection)==0);
}

bool MaxsiThreadSystemPthreads::LeaveCriticalSection(MaxsiHandle CriticalSection)
{
	return (pthread_mutex_unlock((pthread_mutex_t*)CriticalSection)==0);
}

MaxsiHandle MaxsiThreadSystemPthreads::CreateEvent()
{
	MaxsiHandle*	Buffer	=	new MaxsiHandle[2];

	if ( Buffer == NULL ) { return NULL; }
	
	Buffer[0]	=	CreateCriticalSection();

	if ( Buffer[0] == NULL ) { delete[] Buffer; return NULL; }

	Buffer[1]	=	(MaxsiHandle)new pthread_cond_t;

	if ( Buffer[1] == NULL ) { DeleteCriticalSection(Buffer[0]); delete[] Buffer; return NULL; }

	if ( pthread_cond_init((pthread_cond_t*)Buffer[1], NULL) != 0 )
	{
		DeleteCriticalSection(Buffer[0]);
		delete[] Buffer;
		return NULL;
	}

	return (MaxsiHandle)Buffer;
}

bool MaxsiThreadSystemPthreads::WaitForEvent(MaxsiHandle Event)
{
	if ( EnterCriticalSection(((MaxsiHandle*)Event)[0]) == false ) { return false; }
	
	return (pthread_cond_wait((pthread_cond_t*)(((MaxsiHandle*)Event)[1]), (pthread_mutex_t*)((MaxsiHandle*)Event)[0])==0);
}


bool MaxsiThreadSystemPthreads::WaitedForEvent(MaxsiHandle Event)
{
	return LeaveCriticalSection(((MaxsiHandle*)Event)[0]);
}

bool MaxsiThreadSystemPthreads::SignalEventBroadcast(MaxsiHandle Event)
{
	if ( EnterCriticalSection(((MaxsiHandle*)Event)[0]) == false ) { return false; }
	bool Result		=	(pthread_cond_broadcast((pthread_cond_t*)((MaxsiHandle*)Event)[1]) == 0);
	if ( LeaveCriticalSection(((MaxsiHandle*)Event)[0]) == false ) { return false; }
	return Result;	
}

bool MaxsiThreadSystemPthreads::SignalEvent(MaxsiHandle Event)
{
	if ( EnterCriticalSection(((MaxsiHandle*)Event)[0]) == false ) { return false; }
	bool Result		=	(pthread_cond_signal((pthread_cond_t*)((MaxsiHandle*)Event)[1]) == 0);
	if ( LeaveCriticalSection(((MaxsiHandle*)Event)[0]) == false ) { return false; }
	return Result;	
}

bool MaxsiThreadSystemPthreads::DeleteEvent(MaxsiHandle Event)
{
	MaxsiHandle*	Buffer	=	(MaxsiHandle*)Event;

	DeleteCriticalSection(Buffer[0]);

	pthread_cond_destroy((pthread_cond_t*)(Buffer[1]));

	delete (pthread_cond_t*)(Buffer[1]);

	delete[] Buffer;

	return true;	
}

#endif

//=============================================================================
//	Implement threads on Microsoft Windows.
//=============================================================================
#ifdef Maxsi_Support_W32

DWORD WINAPI W32HelloIAmANewThread(LPVOID Parameter)
{
	MaxsiThreadEntry_t	Function			=	((MaxsiThreadParameter*)Parameter)->Function;
	BYTE*				FunctionParameter	=	((MaxsiThreadParameter*)Parameter)->FunctionParameter;

	delete (MaxsiThreadParameter*)(Parameter);
	
	Function(FunctionParameter);
	
	return 0;
}

MaxsiHandle MaxsiThreadSystemW32::CreateThread(MaxsiThreadEntry_t Entry, BYTE* Argument, size_t StackSize)
{
	MaxsiThreadParameter*	Parameter	=	new MaxsiThreadParameter;

	if ( Parameter == NULL ) { return NULL; } // MAXSI_ERROR_MEM_ALLOC_FAIL

	Parameter->Function				=	Entry;
	Parameter->FunctionParameter	=	Argument;

	HANDLE	Result	=	::CreateThread(NULL, StackSize, W32HelloIAmANewThread, Parameter, 0, NULL);
	
	if ( Result == NULL ) { delete Parameter; return NULL; }

	return (MaxsiHandle)Result;
}

MaxsiHandle MaxsiThreadSystemW32::ExitThread(int ExitCode)
{
	ExitThread(ExitCode);
	return NULL;
}

MaxsiHandle MaxsiThreadSystemW32::GetThreadId()
{
	MAXSI_TODO("Implement this!");
	return NULL;
}

bool MaxsiThreadSystemW32::CompareThreadIds(MaxsiHandle Handle1, MaxsiHandle Handle2)
{
	return (Handle1 == Handle2);
}

MaxsiHandle MaxsiThreadSystemW32::CreateCriticalSection()
{
	CRITICAL_SECTION*	CriticalSection	=	new CRITICAL_SECTION;

	if ( CriticalSection == NULL ) { return NULL; }

	if (!InitializeCriticalSectionAndSpinCount(CriticalSection, 0x80000400) ) { delete CriticalSection; return NULL; }

	return (MaxsiHandle)CriticalSection;
}

bool MaxsiThreadSystemW32::DeleteCriticalSection(MaxsiHandle CriticalSection)
{
	::DeleteCriticalSection((LPCRITICAL_SECTION)CriticalSection);

	delete (LPCRITICAL_SECTION)CriticalSection;

	return true;
}

bool MaxsiThreadSystemW32::EnterCriticalSection(MaxsiHandle CriticalSection)
{
	::EnterCriticalSection((LPCRITICAL_SECTION)CriticalSection);
	return true;
}

bool MaxsiThreadSystemW32::TryEnterCriticalSection(MaxsiHandle CriticalSection)
{
	::TryEnterCriticalSection((LPCRITICAL_SECTION)CriticalSection);
	return true;
}

bool MaxsiThreadSystemW32::LeaveCriticalSection(MaxsiHandle CriticalSection)
{
	::LeaveCriticalSection((LPCRITICAL_SECTION)CriticalSection);
	return true;
}

MaxsiHandle MaxsiThreadSystemW32::CreateEvent()
{
	return (MaxsiHandle)::CreateEventM(NULL, FALSE, FALSE, NULL);
}

bool MaxsiThreadSystemW32::WaitForEvent(MaxsiHandle Event)
{
	return (WaitForSingleObject((HANDLE)Event, INFINITE) == WAIT_OBJECT_0);
}

bool MaxsiThreadSystemW32::WaitedForEvent(MaxsiHandle Event)
{
	return true;
}

bool MaxsiThreadSystemW32::SignalEventBroadcast(MaxsiHandle Event)
{
	return false; // MAXSI_ERROR_NOT_SUPPORTED or MAXSI_ERROR_NOT_IMPLEMENTED
}

bool MaxsiThreadSystemW32::SignalEvent(MaxsiHandle Event)
{
	return (SetEvent((HANDLE)Event) != FALSE);
}

bool MaxsiThreadSystemW32::DeleteEvent(MaxsiHandle Event)
{
	CloseHandle((HANDLE)Event);
	return true;
}

#endif

EndMaxsiNamespace

